from __future__ import annotations

import threading
import time
import pathlib
import yaml
from typing import Dict, Any, Callable, Optional
from src.infrastructure.logger import setupLogger

logger = setupLogger(__name__)


class ConfigWatcher(threading.Thread):
    """
    线程安全的 YAML 热重载器
    """

    def __init__(
        self,
        path: pathlib.Path,
        *,
        callback: Callable[[Dict[str, Any]], None],
        interval: float = 0.2,
    ) -> None:
        """
        Args:
            path: 待监视的 YAML 文件
            callback: 差异回调，参数为 dict[点分路径, 新值]
            interval: 轮询间隔（秒）
            logger: 日志实例；为 None 时内部自动创建
        """
        super().__init__(daemon=True)
        self._path: pathlib.Path = path
        self._callback: Callable[[Dict[str, Any]], None] = callback
        self._interval: float = interval
        self._stop_evt = threading.Event()

        # 内部状态
        self._last_mtime: float = 0.0
        self._last_flat: Dict[str, Any] = {}

    # ------------------------------------------------------------------
    # 公共 API
    # ------------------------------------------------------------------
    def start(self) -> None:
        """启动线程（线程安全）"""
        if self.is_alive():
            logger.warning("Watcher 已启动，忽略重复调用")
            return
        super().start()

    def stop(self, timeout: Optional[float] = None) -> None:
        """优雅停止"""
        self._stop_evt.set()
        if self.is_alive():
            self.join(timeout)

    # ------------------------------------------------------------------
    # 线程主循环
    # ------------------------------------------------------------------
    def run(self) -> None:
        logger.info("开始监视 %s", self._path)
        while not self._stop_evt.is_set():
            try:
                mtime = self._path.stat().st_mtime
                if mtime != self._last_mtime:
                    self._reload_and_diff()
                    self._last_mtime = mtime
            except Exception as e:
                logger.warning("轮询异常：%s", e, exc_info=True)
            time.sleep(self._interval)
        logger.info("停止监视 %s", self._path)

    # ------------------------------------------------------------------
    # 内部工具
    # ------------------------------------------------------------------
    def _reload_and_diff(self) -> None:
        """加载 -> 扁平化 -> 计算差异 -> 回调"""
        try:
            with self._path.open("r", encoding="utf-8") as f:
                raw = yaml.safe_load(f) or {}
        except Exception as e:
            logger.error("YAML 加载失败：%s", e)
            return

        new_flat = self._flatten("", raw)
        diff: Dict[str, Any] = {}

        # 新增或变更
        for k, v_new in new_flat.items():
            if self._last_flat.get(k) != v_new:
                diff[k] = v_new
        # 删除
        for k in self._last_flat:
            if k not in new_flat:
                diff[k] = None  # 标记删除

        if diff:
            logger.debug("检测到 %d 项变化", len(diff))
            try:
                self._callback(diff)
            except Exception as e:
                logger.error("回调异常：%s", e, exc_info=True)

        self._last_flat = new_flat

    # ------------------------------------------------------------------
    # 扁平化实现（类私有）
    # ------------------------------------------------------------------
    @staticmethod
    def _flatten(prefix: str, obj: Any) -> Dict[str, Any]:
        """
        递归扁平化任意嵌套结构(dict/list/标量)
        返回 dict[点分路径, 叶子值]
        """
        flat: Dict[str, Any] = {}
        if isinstance(obj, dict):
            for k, v in obj.items():
                new_prefix = f"{prefix}.{k}" if prefix else k
                flat.update(ConfigWatcher._flatten(new_prefix, v))
        elif isinstance(obj, list):
            for idx, v in enumerate(obj):
                new_prefix = f"{prefix}.{idx}" if prefix else str(idx)
                flat.update(ConfigWatcher._flatten(new_prefix, v))
        else:  # 叶子
            key = prefix or "<root>"
            flat[key] = obj
        return flat
